<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head><meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"/>
<title>多对多映射</title><link href="../zdoc.css" rel="stylesheet" type="text/css"/><link href="../_rs/site.css" rel="stylesheet" type="text/css"/><script src="../_rs/jquery.js" language="Javascript"></script><script src="../_rs/site.js" language="Javascript"></script><script src="../_rs/z.js" language="Javascript"></script>
</head>
<body><a name="top"></a>
<div class="zdoc_header">多对多映射</div>
<div class="zdoc_author"><em>By:</em><b>zozoh</b><a href="mailto:zozohtnt@gmail.com">&lt;zozohtnt@gmail.com&gt;</a></div>
<div class="zdoc_body">
<ul class="zdoc_index_table">
<li>
<div><span class="num">1</span><a href="#什么是多对多映射">什么是多对多映射</a></div>
</li>
<li>
<div><span class="num">2</span><a href="#在_POJO_中配置多对多映射">在 POJO 中配置多对多映射</a></div>
</li>
<li>
<div><span class="num">3</span><a href="#NutDao_是如何连接关联表的">NutDao 是如何连接关联表的</a></div>
</li>
<li>
<div><span class="num">4</span><a href="#你不仅可以在集合类型字段上声明一对多映射">你不仅可以在集合类型字段上声明一对多映射</a></div>
</li>
<li>
<div><span class="num">5</span><a href="#插入操作">插入操作</a></div>
</li>
<li>
<div><span class="num">6</span><a href="#获取操作">获取操作</a></div>
</li>
<li>
<div><span class="num">7</span><a href="#更新操作">更新操作</a></div>
</li>
<li>
<div><span class="num">8</span><a href="#删除操作">删除操作</a></div>
</li>
</ul>
<div class="hr"><b></b></div>
<h1><a name="什么是多对多映射"></a>什么是多对多映射</h1>
<div style="float:right;"><a href="#top">Top</a></div>
<p>有两张数据表，通过第三张数据表来表示关联关系，我们称之为多对多的映射</p>
<p><img src="links_manymany.png"/></p>
<p>如上图，通过一个中间数据表的两个字段，分别指向两个对象的主键，可以实现多对多映射。所以，Pet.foods（一个 List&lt;Food&gt;）或者 Food.pets（一个List&lt;Pet&gt;）就是多对多映射。</p>
<div class="hr"><b></b></div>
<h1><a name="在_POJO_中配置多对多映射"></a>在 POJO 中配置多对多映射</h1>
<div style="float:right;"><a href="#top">Top</a></div>
<p>在 POJO 类中字段中增加注解 <b>@ManyMany</b>：</p>
<pre>@Table("t_food")
public class Food extends Pojo {

	@ManyMany(target = Pet.class, relation = "t_pet_food", from = "foodid", to = "petid")
	private List&lt;Pet&gt; pets;

	public List&lt;Pet&gt; getPets() {
		return pets;
	}

	public void setPets(List&lt;Pet&gt; pets) {
		this.pets = pets;
	}

}
</pre>
<p>在 Food 对象中必须存在一个 List&lt;Pet&gt; 类型的字段，你的多对多映射就需要配置在这个字段上。通过 <b>@ManyMany</b> 注解告诉 Nutz.Dao对象 Food 和 Pet 之间的关系，其中：</p>
<ul type="disc">
<li>target 表示你要映射的对象类型</li>
<li>relation 为中间数据表的表名，它也支持<a href="dynamic_table_name.html">动态表名</a></li>
<li>from 是中间数据表的字段名，这个字段将储存主对象的主键（上例的 Food 的主键）</li>
<li>to  是中间数据表的字段名，这个字段将储存映射对象的主键（上例的 Pet 的主键）</li>
</ul>
<p>因此：</p>
<ul type="disc">
<li>数据库中<b>必须</b>存在一个中间表 t_pet_food
<ul type="circle">
<li>该表有一个字段 foodid 对应到 Food 对象的主键</li>
<li>该表有一个字段 petid 对应到 Pet 对象的主键</li>
</ul>
</li>
<li>Nutz.Dao 通过 @ManyMany 这四个属性了解到：
<ul type="circle">
<li>目标的 POJO 类 ： Pet</li>
<li>关联表（<span style="color:#999999;">或者说：中间表</span>）：t_pet_food</li>
<li>关联表的 foodid 字段对应到是本 POJO （<span style="color:#999999;">Food</span>）主键</li>
<li>关联表的 petid 字段对应到是目标 POJO （<span style="color:#999999;">Pet</span>） 主键</li>
</ul>
</li>
</ul>
<div class="hr"><b></b></div>
<h1><a name="NutDao_是如何连接关联表的"></a>NutDao 是如何连接关联表的</h1>
<div style="float:right;"><a href="#top">Top</a></div>
<p>比如，下面的例子</p>
<pre>我们有两个 POJO
public class Pet {
    @Id
    public int id;
    @Name
    public String name;
}
//-------------------------------
public class Food {
    @Id
    public int id;
    @Name
    public String name;
}
</pre>
<p>我们设计了一个关联表关联这两个对象，表示一个宠物爱吃什么样的食物</p>
<pre>t_pet_food
===================
 pid | fid
-----+------
 3   | 6
 9   | 8
</pre>
<p>那么我们可以为 Pet 类声明一个多对多关联</p>
<pre>public class Pet {
    @Id
    public int id;
    @Name
    public String name;
    @ManyMany(target=Food.class, 
              relation="t_pet_food", 
              from="pid", 
              to="fid")
    public List&lt;Food&gt; foods;
}
</pre>
<p>可以看到，我们指明了，Pet 类的 foods 字段，通过中间表 t_pet_food 来获取一组 Food 对象。因为 @ManyMany 是声明在 Pet 类的字段上的，那么 Pet 类就被称为所谓的"宿主对象"，而 Food则是所谓的"目标对象"。from 指明关联表的 pid 字段的值代表宿主对象，而 to 指明 fid字段代表目标对象。 </p>
<p>看到这里，有的心思缜密的同学肯定会有一个小小怀疑，NutDao 怎么能知道 pid 对应到 Pet 哪个字段呢？靠猜吗？恭喜你，答对了。NutDao 在解析到这个注解的时候，会看看 Pet 类，你是否在某个字段上声明了 @Id 注解，如果没有，则试图看看你有没有在某个字段上声明了 @Name 注解。当然，如果你没有声明 @Id 注解，而用整数字段作为 pid 一定会出错的，你必须把关联表改成:</p>
<pre>t_pet_food
===================
 pid       | fid
-----------+------
 xiaobai   | 6
 xiaoqiang | 8
</pre>
<p>这个约定有点死板对吗？并且如果你用 VARCHAR 作为 pid 的数据表字段类型，但是你的 Pet 却声明了 @Id 注解，一样会错，因为 @Id 注解优先。</p>
<p>读到这里，你一定感到很郁闷，因为你实在不想改变你的关联表，没关系，你可以下面一样声明你的 @ManyMany 注解</p>
<pre>...
@ManyMany(target=Food.class, 
          relation="t_pet_food", 
          from="pid:name", 
          to="fid")
public List&lt;Food&gt; foods;
...
</pre>
<p>看，你为 from 声明了一个 "pid:name"，这个冒号后面的，就是大声告诉 NutDao 请用 Pet.name来映射这个 pid 字段。这样 NutDao 就不会自己瞎猜了。</p>
<p>同理，to 也有一样的属性。并且冒号后面的并不用一定是 PK 字段，只要是惟一性字段均可</p>
<p>说到这里，我不得不解释一下，NutDao 主要是通过你提供的注解来分析 POJO 的。我曾经考虑过，是不是少让大家提供几个注解，我自行分析数据表，然后总能做出合理的决定。但是 ... 数据种类实在太多了，各自有各自的脾气，从植物学的角度来说，这很有可能是一个一望无际的大坑，如果我那么做了，估计我会有相当长的一段时间在坑里幸福的遨游，所以我收起了自己的胆量，弱弱的给出了一组注解，以便我能用更少的代码做更多的事情。当然，就 @ManyMany 这个用法，用冒号分隔，通过注解指明映射字段，我想应该还是可以被多数人接受的。</p>
<div class="hr"><b></b></div>
<h1><a name="你不仅可以在集合类型字段上声明一对多映射"></a>你不仅可以在集合类型字段上声明一对多映射</h1>
<div style="float:right;"><a href="#top">Top</a></div>
<p>本 POJO 类的 @Many 映射，可以不止声明在 List 对象上，它还可以声明在</p>
<ul type="disc">
<li>数组</li>
<li>Map</li>
<li>POJO</li>
</ul>
<p>详情，可以参看 <a href="links_many.html">一对多映射</a> 的相关描述</p>
<div class="hr"><b></b></div>
<h1><a name="插入操作"></a>插入操作</h1>
<div style="float:right;"><a href="#top">Top</a></div>
<p>如果你已经实现准备好了这样的对象：</p>
<pre>Food food = new Food("Fish");

List&lt;Pet&gt; pets = new ArrayList&lt;Pet&gt;();
pets.add(new Pet("XiaoBai");
pets.add(new Pet("XiaoHei");

food.setPets(pets);
</pre>
<p>那么你可以一次将 food 以及它对应的 pets 一起插入到数据表中，并在关联表中插入对应的记录</p>
<pre>dao.insertWith(food, "pets");
</pre>
<p>Nutz.Dao 会根据正则表达式 "pets" 寻找可以被匹配上的映射字段（只要声明了 @One, @Many, @ManyMany 任何一个注解，都是映射字段）并根据注解具体的配置信息，执行相应的 SQL。比如上面的操作，会实际上：</p>
<pre>执行 SQL : INSERT INTO t_food (name) VALUES("Fish");
执行 SQL 获取 最大值： SELECT MAX(id) FROM t_food  // 假设返回的值是 6
循环 food.pets
	执行 SQL: INSERT INTO t_pet (name) VALUES("XiaoBai");
	执行 SQL 获取 最大值： SELECT MAX(id) FROM t_pet  // 假设返回的值是 97
	执行 SQL 插入关联: INSERT INTO t_pet_food (foodid, petid) VALUES(6, 97);
	...
</pre>
<p>这里通过 SELECT MAX 来获取插入的最大值，是默认的做法，如果你想修改这个默认做法，请参看 <a href="primary_key.html">关于主键</a>一章。</p>
<ul type="disc">
<li>这里因为是多对多映射，所以会首先插入主对象并循环插入映射对象，以便获得双发的主键</li>
<li>如果你的对象中包括多个 @ManyMany 字段，被你的正则式匹配上，那么这些字段对应的字段（如果不为null）都会被匹配，一次被执行</li>
</ul>
<p>当然，你要想选择仅仅只插入映射字段的话，你可以：</p>
<pre>dao.insertLinks(food,"pets");
</pre>
<p>如果 food.id 的值为 6，那么上述操作实际上会执行：</p>
<pre>循环 food.pets
	执行 SQL: INSERT INTO t_pet (name) VALUES("XiaoBai");
	执行 SQL 获取 最大值： SELECT MAX(id) FROM t_pet  // 假设返回的值是 97
	执行 SQL 插入关联: INSERT INTO t_pet_food (foodid, petid) VALUES(6, 97);
	...
</pre>
<p>看，并不会插入 food 对象。</p>
<p>如果你已经存在了 food 和 pets 对象，你仅仅打算将它们关联起来，那么你可以</p>
<pre>dao.insertRelation(food,"pets");
</pre>
<p>如果 food.id 的值为 6，那么上述操作实际上会执行：</p>
<pre>循环 food.pets
	执行 SQL 插入关联: INSERT INTO t_pet_food (foodid, petid) VALUES(6, 97);
	...
</pre>
<p>看，仅仅只会插入 food 和 pets 的关联</p>
<div class="hr"><b></b></div>
<h1><a name="获取操作"></a>获取操作</h1>
<div style="float:right;"><a href="#top">Top</a></div>
<p>仅仅获取映射对象：</p>
<pre>Food food = dao.fetch(Food.class, "Fish");
dao.fetchLinks(food, "pets");
</pre>
<p>这会执行操作：</p>
<pre>执行 SQL: SELECT * FROM t_food WHERE name='Fish'; // 如果 food.id 是6
执行 SQL: SELECT * FROM t_pet WHERE id IN (SELECT petid FROM t_pet_food WHERE foodid=6)
</pre>
<p>但是 Nutz.Dao 没有提供一次获取 food 对象以及 pets 对象的方法，因为，你完全可以把上面的两句话写在一行上：</p>
<pre>Food food = dao.fetchLinks(dao.fetch(Food.class, "Fish"), "pets");
</pre>
<p>然后，你可以通过 food.getPets() 得到 Nutz.Dao 为 food.pets 字段设置的值。</p>
<div class="hr"><b></b></div>
<h1><a name="更新操作"></a>更新操作</h1>
<div style="float:right;"><a href="#top">Top</a></div>
<p>同时更新 food 和 pet</p>
<pre>dao.updateWith(food, "pets");
</pre>
<p>这会执行</p>
<pre>执行SQL: UPDATE t_food ....
循环 food.pets 并依次执行SQL: UPDATE t_pet ...
</pre>
<p>仅仅更新 pets</p>
<pre>dao.updateLinks(food, "pets");
</pre>
<p>这会执行</p>
<pre>循环 food.pets 并依次执行SQL: UPDATE t_pet ...
</pre>
<div class="hr"><b></b></div>
<h1><a name="删除操作"></a>删除操作</h1>
<div style="float:right;"><a href="#top">Top</a></div>
<p>同时删除 food 和 pets</p>
<pre>dao.deleteWith(food, "pets");
</pre>
<p>仅仅删除 pets</p>
<pre>dao.deleteLinks(food, "pets");
</pre>
<p>清除 pets</p>
<pre>dao.clearLinks(food, "pets");
</pre>
<p>清除同删除的区别在于，清除只会执行一条 SQL 删除 t_pet_food 的记录，但是 t_pet 和 t_food 表中的数据不会被删除而删除则不仅会清除 t_pet_food 里的记录，还会逐个调用 dao.delete 来删除对象。</p>
</div>
<div class="zdoc_footer"><em>By:</em><b>zozoh</b><a href="mailto:zozohtnt@gmail.com">&lt;zozohtnt@gmail.com&gt;</a></div>
</body>
</html>